#include "libmad_driver.h"

#include "mad.h"

#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>

#define BUFSIZE 8192

struct Libmad_driver::mp3_file {
    FILE*              file_ptr;
    unsigned int       file_len;
    unsigned int       file_pos;
    unsigned char      file_buf[BUFSIZE];
    unsigned int       buf_size;
    unsigned int       isFindFirstFrame;
    unsigned int       file_duration;
    unsigned long      frame_start;
    unsigned int       frame_num;
    unsigned int       frame_duration_ms;
    struct mad_decoder decoder;
    int (*output_handler)(uint8_t* data, int length, int frq, int channels);
};

Libmad_driver::Libmad_driver() {
    mp3_data_ = new mp3_file;
}

Libmad_driver::~Libmad_driver() {
    if (mp3_data_->file_ptr) {
        fclose(mp3_data_->file_ptr);
    }
    delete mp3_data_;
}

void Libmad_driver::initialize(
    const char* new_filename, int (*outputHandler)(const short* pcm, size_t num_samples, int frq, int channels)) {
    if (mp3_data_->file_ptr != nullptr) {
        fclose(mp3_data_->file_ptr);
        mp3_data_->file_ptr = nullptr;
    }

    mp3_data_->file_ptr = fopen(new_filename, "rb");
    if (!mp3_data_->file_ptr) {
        fprintf(stderr, "Failed to open MP3 file: %s\n", new_filename);
        return;
    }

    mp3_data_->file_pos = 0;
    fseek(mp3_data_->file_ptr, 0, SEEK_END);
    mp3_data_->file_len = ftell(mp3_data_->file_ptr) - 256;
    fseek(mp3_data_->file_ptr, 0, SEEK_SET);

    if (mp3_data_->frame_start == (unsigned long)-1) {
        mp3_data_->isFindFirstFrame = 0;
        mp3_data_->frame_start = 0;
    }

    fseek(mp3_data_->file_ptr, mp3_data_->frame_start, SEEK_CUR);
    mp3_data_->frame_start = 256;
    mp3_data_->file_pos = mp3_data_->frame_start;
    mp3_data_->output_handler = reinterpret_cast<int (*)(uint8_t*, int, int, int)>(outputHandler);

    mad_decoder_init(&mp3_data_->decoder, mp3_data_, input, 0, 0, output, 0, 0);
    mad_decoder_run(&mp3_data_->decoder, MAD_DECODER_MODE_SYNC);
    mad_decoder_finish(&mp3_data_->decoder);
}

enum mad_flow Libmad_driver::input(void* data, struct mad_stream* stream) {
    auto* mp3 = reinterpret_cast<mp3_file*>(data);
    int   unprocessed_size = stream->bufend - stream->next_frame;
    int   copy_size = BUFSIZE - unprocessed_size;

    std::memmove(mp3->file_buf, mp3->file_buf + mp3->buf_size - unprocessed_size, unprocessed_size);

    if (mp3->file_pos + copy_size > mp3->file_len) {
        copy_size = mp3->file_len - mp3->file_pos;
    }

    mp3->buf_size = unprocessed_size + copy_size;
    mp3->file_pos += copy_size;

    fread(mp3->file_buf + unprocessed_size, 1, copy_size, mp3->file_ptr);
    mad_stream_buffer(stream, mp3->file_buf, mp3->buf_size);

    return (mp3->file_pos < mp3->file_len) ? MAD_FLOW_CONTINUE : MAD_FLOW_STOP;
}

enum mad_flow Libmad_driver::output(void* data, struct mad_header const* header, struct mad_pcm* pcm) {
    auto* mp3 = reinterpret_cast<mp3_file*>(data);

    if (!mp3->isFindFirstFrame) {
        unsigned int samples_per_frame = MAD_NSBSAMPLES(header) * 32;
        mp3->frame_duration_ms = samples_per_frame * 1000 / header->samplerate;
        mp3->isFindFirstFrame = 1;

        unsigned int frame_size = 144 * header->bitrate / header->samplerate;
        int          total_frames = (mp3->file_len - mp3->file_pos) / frame_size;
        mp3->file_duration = total_frames * mp3->frame_duration_ms;
        mp3->frame_num = 0;
    }

    unsigned int       nchannels = pcm->channels;
    unsigned int       nsamples = pcm->length;
    const mad_fixed_t* left_ch = pcm->samples[0];
    const mad_fixed_t* right_ch = pcm->samples[1];

    uint8_t* buf = static_cast<uint8_t*>(malloc(nchannels * nsamples * 2));
    if (!buf) {
        fprintf(stderr, "Failed to allocate PCM buffer\n");
        return MAD_FLOW_STOP;
    }

    int i = 0;
    while (nsamples--) {
        signed int sample = scale(*left_ch++);
        buf[i] = sample & 0xFF;
        buf[i + 1] = (sample >> 8) & 0xFF;

        if (nchannels == 2) {
            sample = scale(*right_ch++);
            buf[i + 2] = sample & 0xFF;
            buf[i + 3] = (sample >> 8) & 0xFF;
        }

        i += 2 * nchannels;
    }

    int ret = 0;
    if (mp3->output_handler) ret = mp3->output_handler(buf, i, header->samplerate, nchannels);

    free(buf);
    if (ret == -1) return MAD_FLOW_STOP;

    mp3->frame_num++;
    return MAD_FLOW_CONTINUE;
}

enum mad_flow Libmad_driver::error(void* data, struct mad_stream* stream, struct mad_frame* frame) {
    auto* mp3 = reinterpret_cast<mp3_file*>(data);
    fprintf(
        stderr, "Decoding error 0x%04x (%s) at byte offset %ld\n", stream->error, mad_stream_errorstr(stream),
        stream->this_frame - mp3->file_buf);
    return MAD_FLOW_CONTINUE;
}

inline signed int Libmad_driver::scale(mad_fixed_t sample) {
    /* round */
    sample += (1L << (MAD_F_FRACBITS - 16));

    /* clip */
    if (sample >= MAD_F_ONE)
        sample = MAD_F_ONE - 1;
    else if (sample < -MAD_F_ONE)
        sample = -MAD_F_ONE;

    /* quantize */
    return sample >> (MAD_F_FRACBITS + 1 - 16);
}